﻿/* ***********************************************
 * Author		:  kingthy
 * Email		:  kingthy@gmail.com
 * Description	:  DbObject
 *
 * ***********************************************/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;

using System.Collections;
using System.ComponentModel;
using MX.Core.Extensions;

namespace MX.Core.Data
{
    /// <summary>
    /// 数据对象
    /// </summary>
    /// <remarks>此对象一般用作于数据库实例对象的基类</remarks>
    /// <example>
    /// <code>
    /// <![CDATA[
    /// /// <summary>
    /// /// 地址对象
    /// /// </summary>
    /// public class Location : DbObject
    /// {
    ///     /// <summary>
    ///     /// 地址
    ///     /// </summary>
    ///     public string Address
    ///     {
    ///         get
    ///         {
    ///             return this.GetData<string>("Address", null);
    ///         }
    ///         set
    ///         {
    ///             this["Address"] = value;
    ///         }
    ///     }
    ///     /// <summary>
    ///     /// 邮政
    ///     /// </summary>
    ///     public string Zipcode
    ///     {
    ///         get
    ///         {
    ///             return this.GetData<string>("Zipcode", null);
    ///         }
    ///         set
    ///         {
    ///             this["Zipcode"] = value;
    ///         }
    ///     }
    /// }
    /// /// <summary>
    /// /// 用户对象
    /// /// </summary>
    /// public class User : DbObject
    /// {
    ///     public User()
    ///     {
    ///         this.Location = new Location();
    ///         this.AddInternalData("Address", () => this.Location.Address, d => this.Location.Address = d.As<string>());
    ///         this.AddInternalData("Zipcode", () => this.Location.Zipcode, d => this.Location.Zipcode = d.As<string>());
    ///     }
    ///     /// <summary>
    ///     /// 地址
    ///     /// </summary>
    ///     public readonly Location Location;
    ///     /// <summary>
    ///     /// 姓名
    ///     /// </summary>
    ///     public string Name
    ///     {
    ///         get
    ///         {
    ///             return this.GetData<string>("Name", null);
    ///         }
    ///         set
    ///         {
    ///             this["Name"] = value;
    ///         }
    ///     }
    ///     /// <summary>
    ///     /// 年龄
    ///     /// </summary>
    ///     public int Age
    ///     {
    ///         get
    ///         {
    ///             return this.GetData<int>("Age", 0);
    ///         }
    ///         set
    ///         {
    ///             this["Age"] = value;
    ///         }
    ///     }
    /// }
    /// User user = new User();
    /// user.Age = 1;
    /// Console.WriteLine(user.Age);
    /// user.Location.Address = "广东";
    /// Console.WriteLine(user.Location.Address);
    /// user["Address"] = "广州";
    /// Console.WriteLine(user.Location.Address);
    /// ]]>
    /// </code>
    /// </example>
    public class DbObject
        : ICustomTypeDescriptor
    {
        /// <summary>
        /// 采用默认容量初始化实例
        /// </summary>
        public DbObject()
            : this(16)
        { }
        /// <summary>
        /// 根据初始容量初始化实例
        /// </summary>
        public DbObject(int capacity)
        {
            this._slot = new Hashtable(capacity, StringComparer.OrdinalIgnoreCase);
        }

        /// <summary>
        /// 数据槽
        /// </summary>
        private Hashtable _slot;
        /// <summary>
        /// 数据名称的缓存
        /// </summary>
        private string[] DataNamesCache;
        /// <summary>
        /// 内部定义的数据
        /// </summary>
        private Dictionary<string,InternalData> InternalDatas;
        /// <summary>
        /// 内部数据
        /// </summary>
        private class InternalData
        {
            /// <summary>
            /// 
            /// </summary>
            /// <param name="get"></param>
            /// <param name="set"></param>
            public InternalData(Func<object> get, Action<object> set)
            {
                this.Get = get;
                this.Set = set;
            }
            /// <summary>
            /// GET方法
            /// </summary>
            public Func<object> Get { get; private set; }
            /// <summary>
            /// SET方法
            /// </summary>
            public Action<object> Set { get; private set; }
        }

        /// <summary>
        /// 设置或返回数据对象中的某个数据
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public object this[string name]
        {
            get
            {
                bool existing;
                return this.GetDataInternal(name, out existing);
            }
            set
            {
                this.SetData(name, value);
            }
        }

        /// <summary>
        /// 增加内部定义的数据
        /// </summary>
        /// <param name="name">数据名称</param>
        /// <param name="get">获取数据的方法</param>
        /// <param name="set">设置数据的方法</param>
        protected void AddInternalData(string name, Func<object> get, Action<object> set)
        {
            if (this.InternalDatas == null)
                this.InternalDatas = new Dictionary<string, InternalData>(StringComparer.OrdinalIgnoreCase);
            if (!this.InternalDatas.ContainsKey(name))
            {
                this.InternalDatas.Add(name, new InternalData(get,set));
                this.OnObjectChanged();
            }
        }

        /// <summary>
        /// 判断数据对象是否存在某个数据
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public bool ContainData(string name)
        {
            if (this.InternalDatas != null && this.InternalDatas.ContainsKey(name)) return true;
            return this._slot.ContainsKey(name);
        }
        /// <summary>
        /// 数据对象有改变
        /// </summary>
        protected virtual void OnObjectChanged()
        {
            this.DataNamesCache = null;
            this.PropertyDescriptorCollectionCache = null;
        }
        /// <summary>
        /// 将数据加入到数据对象前调用的函数, 如果返回false则将阻止添加数据，否则继续添加数据。
        /// 派生类可以重写此方法用于处理某些特殊的数据。比如将"BookName"与"BookTitle"都可以看成同一个name的数据。
        /// </summary>
        /// <param name="name"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        protected virtual bool OnBeforeSetData(string name, object value)
        {
            return true;
        }

        /// <summary>
        /// 将数据加入到数据对象中。
        /// </summary>
        /// <param name="name">数据名称</param>
        /// <param name="value">数据值</param>
        /// <exception cref="System.ArgumentNullException">数据名称为空时引发</exception>
        public void SetData(string name, object value)
        {
            lock (this._slot.SyncRoot)
            {
                this.SetDataInternal(name, value);
            }
        }

        /// <summary>
        /// 将数据加入到数据对象中，此方法会优先调用OnBeforeSetData方法判断是否可将数据加入槽中
        /// </summary>
        /// <param name="name">数据名称</param>
        /// <param name="value">数据值</param>
        /// <exception cref="System.ArgumentNullException">数据名称为空时引发</exception>
        public void SetDataInternal(string name, object value)
        {
            if (!this.OnBeforeSetData(name, value)) return;

            this.SetDataImp(name, value);
        }
        /// <summary>
        /// 直接将数据加入到数据对象中，此方法不会调用OnBeforeSetData方法
        /// </summary>
        /// <param name="name">数据名称</param>
        /// <param name="value">数据值</param>
        /// <exception cref="System.ArgumentNullException">数据名称为空时引发</exception>
        protected void SetDataImp(string name, object value)
        {
            if (string.IsNullOrEmpty(name)) throw new ArgumentNullException("name", "数据名称不能为空");
            
            if (this.InternalDatas != null
                && this.InternalDatas.ContainsKey(name))
            {
                //优先处理内容数据
                var data = this.InternalDatas[name];
                if (data.Set != null) data.Set(value);
            }
            else
            {
                if (this._slot.ContainsKey(name))
                {
                    this._slot[name] = value;
                }
                else
                {
                    this._slot.Add(name, value);
                    this.OnObjectChanged();
                }
            }
        }

        /// <summary>
        /// 获取数据对象的某个数据，如果不存在数据，则返回null
        /// </summary>
        /// <param name="name">数据名称</param>
        /// <param name="existing">返回当前数据对象是否已存在有此数据</param>
        /// <returns></returns>
        internal object GetDataInternal(string name, out bool existing)
        {
            if (string.IsNullOrEmpty(name)) throw new ArgumentNullException("name", "数据名称不能为空");
            if (this.InternalDatas != null
                 && this.InternalDatas.ContainsKey(name))
            {
                //优先处理内容数据
                existing = true;
                var data = this.InternalDatas[name];
                return data.Get == null ? null : data.Get();
            }
            else
            {
                existing = this._slot.ContainsKey(name);
                return existing ? this._slot[name] : null;
            }
        }
        /// <summary>
        /// 获取数据对象的某个数据，如果不存在此数据，则返回对应类型的默认值
        /// </summary>
        /// <typeparam name="T">获取的数据类型</typeparam>
        /// <param name="name">数据名称</param>
        /// <returns></returns>
        public T GetData<T>(string name)
        {
            bool existing;
            object value = this.GetDataInternal(name, out existing);
            return existing ? value.As<T>() : default(T);
        }

        /// <summary>
        /// 获取数据对象的某个数据
        /// </summary>
        /// <typeparam name="T">获取的数据类型</typeparam>
        /// <param name="name">数据名称</param>
        /// <param name="replacement">如果获取失败则返回此代替值</param>
        /// <returns></returns>
        public T GetData<T>(string name, T replacement)
        {
            bool existing;
            object value = this.GetDataInternal(name, out existing);
            return existing ? value.As<T>(replacement) : replacement;
        }

        /// <summary>
        /// 获取数据对象的所有数据名称
        /// </summary>
        /// <returns></returns>
        public string[] GetAllDataNames()
        {
            if (this.DataNamesCache == null)
            {
                //重新获取
                lock (this._slot.SyncRoot)
                {
                    if (this.InternalDatas == null
                        || this.InternalDatas.Count == 0)
                    {
                        this.DataNamesCache = new string[this._slot.Count];
                        this._slot.Keys.CopyTo(this.DataNamesCache, 0);
                    }
                    else
                    {
                        List<string> names = new List<string>(this.InternalDatas.Keys);
                        foreach (string k in this._slot.Keys)
                        {
                            if (!this.InternalDatas.ContainsKey(k)) names.Add(k);
                        }
                        this.DataNamesCache = names.ToArray();
                    }
                }
            }
            return this.DataNamesCache;
        }
        
        /// <summary>
        /// 返回数据对象中的数据数量
        /// </summary>
        public int GetDataCount()
        {
            return this.GetAllDataNames().Length;
        }

        /// <summary>
        /// 将此数据对象的数据拷到另外的数据对象上
        /// </summary>
        /// <param name="obj"></param>
        public void CopyTo(DbObject obj)
        {
            var names = this.GetAllDataNames();
            bool existing;
            lock (obj._slot.SyncRoot)
            {
                foreach (var name in names)
                {
                    obj.SetDataInternal(name, this.GetDataInternal(name, out existing));
                }
            }
        }
        #region ICustomTypeDescriptor 成员
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        AttributeCollection ICustomTypeDescriptor.GetAttributes()
        {
            return new AttributeCollection(this.GetType().GetCustomAttributes(true).Cast<Attribute>().ToArray());
        }
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        string ICustomTypeDescriptor.GetClassName()
        {
            return this.GetType().FullName;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        string ICustomTypeDescriptor.GetComponentName()
        {
            return null;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        TypeConverter ICustomTypeDescriptor.GetConverter()
        {
            return new TypeConverter();
        }
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        EventDescriptor ICustomTypeDescriptor.GetDefaultEvent()
        {
            return null;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        PropertyDescriptor ICustomTypeDescriptor.GetDefaultProperty()
        {
            return null;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="editorBaseType"></param>
        /// <returns></returns>
        object ICustomTypeDescriptor.GetEditor(Type editorBaseType)
        {
            return null;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="attributes"></param>
        /// <returns></returns>
        EventDescriptorCollection ICustomTypeDescriptor.GetEvents(Attribute[] attributes)
        {
            return EventDescriptorCollection.Empty;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        EventDescriptorCollection ICustomTypeDescriptor.GetEvents()
        {
            return ((ICustomTypeDescriptor)this).GetEvents(null);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="attributes"></param>
        /// <returns></returns>
        PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties(Attribute[] attributes)
        {
            var properties = this.GetPropertyDescriptorCollection();
            if (attributes == null || attributes.Length == 0) return properties;

            List<PropertyDescriptor> items = new List<PropertyDescriptor>();
            foreach (PropertyDescriptor pro in properties)
            {
                if (pro.Attributes.Matches(attributes)) items.Add(pro);
            }
            return new PropertyDescriptorCollection(items.ToArray());
        }
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties()
        {
            return ((ICustomTypeDescriptor)this).GetProperties(null);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="pd"></param>
        /// <returns></returns>
        object ICustomTypeDescriptor.GetPropertyOwner(PropertyDescriptor pd)
        {
            return this;
        }
        /// <summary>
        /// 
        /// </summary>
        private PropertyDescriptorCollection PropertyDescriptorCollectionCache = null;
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        private PropertyDescriptorCollection GetPropertyDescriptorCollection()
        {
            if (PropertyDescriptorCollectionCache == null)
            {
                var componentType = this.GetType();
                var properties = componentType.GetProperties();
                var dataNames = this.GetAllDataNames();
                Dictionary<string, DbObjectPropertyDescriptor> items = new Dictionary<string, DbObjectPropertyDescriptor>(properties.Length + dataNames.Length, StringComparer.OrdinalIgnoreCase);
                //优先处理定义的属性
                foreach (var pro in properties)
                {
                    if(pro.GetIndexParameters().Length == 0)
                        items.Add(pro.Name, new DbObjectPropertyDescriptor(pro.Name, componentType, pro.PropertyType, pro.GetCustomAttributes(true).Cast<Attribute>().ToArray()));
                }
                //处理数据对象里的数据
                foreach (string n in dataNames)
                {
                    if (!items.ContainsKey(n))
                    {
                        var o = this[n];
                        var propertyType = o == null ? typeof(object) : o.GetType();
                        items.Add(n, new DbObjectPropertyDescriptor(n, componentType, propertyType, null));
                    }
                }
                PropertyDescriptorCollectionCache = new PropertyDescriptorCollection(items.Values.ToArray());
            }
            return PropertyDescriptorCollectionCache;
        }
        #endregion
    }
}
